--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / database / __init__.py
Displaying Raw • Download
meshchatx/src/backend/database/__init__.py 9d7ae2017b7a4f378eaebadf3a24bf066e5d3eb9 (9d7ae201) Text, 19.25 KB
Tff7b72import T7ee787json
Tff7b72import T7ee787logging
Tff7b72import T7ee787os
Tff7b72import T7ee787shutil
Tff7b72import T7ee787zipfile
Tff7b72from T7ee787datetime Tff7b72import Te6edf3UTCTb4b4b4, Te6edf3datetime
Tff7b72from T7ee787.T7ee787announces Tff7b72import Te6edf3AnnounceDAO
Tff7b72from T7ee787.T7ee787config Tff7b72import Te6edf3ConfigDAO
Tff7b72from T7ee787.T7ee787contacts Tff7b72import Te6edf3ContactsDAO
Tff7b72from T7ee787.T7ee787debug_logs Tff7b72import Te6edf3DebugLogsDAO
Tff7b72from T7ee787.T7ee787legacy_migrator Tff7b72import Te6edf3LegacyMigrator
Tff7b72from T7ee787.T7ee787map_drawings Tff7b72import Te6edf3MapDrawingsDAO
Tff7b72from T7ee787.T7ee787messages Tff7b72import Te6edf3MessageDAO
Tff7b72from T7ee787.T7ee787misc Tff7b72import Te6edf3MiscDAO
Tff7b72from T7ee787.T7ee787provider Tff7b72import Te6edf3DatabaseProvider
Tff7b72from T7ee787.T7ee787ringtones Tff7b72import Te6edf3RingtoneDAO
Tff7b72from T7ee787.T7ee787schema Tff7b72import Te6edf3DatabaseSchema
Tff7b72from T7ee787.T7ee787telemetry Tff7b72import Te6edf3TelemetryDAO
Tff7b72from T7ee787.T7ee787telephone Tff7b72import Te6edf3TelephoneDAO
Tff7b72from T7ee787.T7ee787voicemails Tff7b72import Te6edf3VoicemailDAO
Te6edf3BACKUP_BASELINE_FILENAME Tff7b72= Ta5d6ff"Ta5d6ffbackup-baseline.jsonTa5d6ff"
Te6edf3MIN_SIZE_RATIO Tff7b72= T79c0ff0.2
Te6edf3_log Tff7b72= Te6edf3loggingTff7b72.Td2a8ffgetLoggerTb4b4b4(Ta5d6ff"Ta5d6ffmeshchatx.databaseTa5d6ff"Tb4b4b4)
Tff7b72class T56d364DatabaseTb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3db_pathTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffprovider Tff7b72= Te6edf3DatabaseProviderTff7b72.Td2a8ffget_instanceTb4b4b4(Te6edf3db_pathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffschema Tff7b72= Te6edf3DatabaseSchemaTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffconfig Tff7b72= Te6edf3ConfigDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmessages Tff7b72= Te6edf3MessageDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffannounces Tff7b72= Te6edf3AnnounceDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmisc Tff7b72= Te6edf3MiscDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72= Te6edf3TelephoneDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelemetry Tff7b72= Te6edf3TelemetryDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffvoicemails Tff7b72= Te6edf3VoicemailDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffringtones Tff7b72= Te6edf3RingtoneDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcontacts Tff7b72= Te6edf3ContactsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmap_drawings Tff7b72= Te6edf3MapDrawingsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdebug_logs Tff7b72= Te6edf3DebugLogsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72def Td2a8ffinitializeTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffschemaTff7b72.Td2a8ffinitializeTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffmigrate_from_legacyTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3reticulum_config_dirTb4b4b4, Te6edf3identity_hash_hexTb4b4b4)Tb4b4b4:
Te6edf3migrator Tff7b72= Te6edf3LegacyMigratorTb4b4b4(
Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4,
Te6edf3reticulum_config_dirTb4b4b4,
Te6edf3identity_hash_hexTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3migratorTff7b72.Td2a8ffshould_migrateTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3migratorTff7b72.Td2a8ffmigrateTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffexecute_sqlTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3queryTb4b4b4, Te6edf3paramsTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
Tff7b72def Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA wal_autocheckpoint=1000Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA temp_store=MEMORYTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA journal_mode=WALTa5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffSQLite pragma setup failed: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_get_pragma_valueTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3pragmaTb4b4b4: Tffa657strTb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3cursor Tff7b72= Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffPRAGMA Tffd700{Te6edf3pragmaTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3row Tff7b72= Te6edf3cursorTff7b72.Td2a8fffetchoneTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3row Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3default
Tff7b72return Te6edf3rowTb4b4b4[T79c0ff0Tb4b4b4]
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Te6edf3default
Tff7b72def Td2a8ff_get_database_file_statsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72def Td2a8ffsize_forTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3pathTb4b4b4)
Tff7b72except Tf85149OSErrorTb4b4b4:
Tff7b72return T79c0ff0
Te6edf3db_path Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffdb_path
Te6edf3wal_path Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-walTa5d6ff"
Te6edf3shm_path Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-shmTa5d6ff"
Te6edf3main_bytes Tff7b72= Te6edf3size_forTb4b4b4(Te6edf3db_pathTb4b4b4)
Te6edf3wal_bytes Tff7b72= Te6edf3size_forTb4b4b4(Te6edf3wal_pathTb4b4b4)
Te6edf3shm_bytes Tff7b72= Te6edf3size_forTb4b4b4(Te6edf3shm_pathTb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffmain_bytesTa5d6ff"Tb4b4b4: Te6edf3main_bytesTb4b4b4,
Ta5d6ff"Ta5d6ffwal_bytesTa5d6ff"Tb4b4b4: Te6edf3wal_bytesTb4b4b4,
Ta5d6ff"Ta5d6ffshm_bytesTa5d6ff"Tb4b4b4: Te6edf3shm_bytesTb4b4b4,
Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4: Te6edf3main_bytes Tff7b72+ Te6edf3wal_bytes Tff7b72+ Te6edf3shm_bytesTb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_get_backup_baseline_pathTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
Te6edf3default_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3default_dirTb4b4b4, Te6edf3BACKUP_BASELINE_FILENAMETb4b4b4)
Tff7b72def Td2a8ff_read_backup_baselineTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
Te6edf3path Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_backup_baseline_pathTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72tryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3data Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)
Tff7b72return Te6edf3data
Tff7b72except Tb4b4b4(Tf85149OSErrorTb4b4b4, Te6edf3jsonTff7b72.Td2a8ffJSONDecodeErrorTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72def Td2a8ff_write_backup_baselineTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4, Te6edf3message_countTb4b4b4, Te6edf3total_bytesTb4b4b4)Tb4b4b4:
Te6edf3path Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_backup_baseline_pathTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3data Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4: Te6edf3message_countTb4b4b4,
Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4: Te6edf3total_bytesTb4b4b4,
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3jsonTff7b72.Td2a8ffdumpTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3fTb4b4b4, Te6edf3indentTff7b72=T79c0ff2Tb4b4b4)
Tff7b72except Tf85149OSError Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to write backup baseline: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_get_current_db_content_statsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3stats Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_database_file_statsTb4b4b4(Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3message_count Tff7b72= Tff7b72selfTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffcount_lxmf_messagesTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Te6edf3message_count Tff7b72= Tff7b72-T79c0ff1
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4: Te6edf3message_countTb4b4b4,
Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4: Te6edf3statsTb4b4b4[Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_is_backup_suspiciousTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3current_statsTb4b4b4, Te6edf3baselineTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3baselineTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3prev_count Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3prev_bytes Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_count Tff7b72= Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_bytes Tff7b72= Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Tff7b72if Te6edf3prev_count Tff7b72> T79c0ff0 Tff7b72and Te6edf3curr_count Tff7b72== T79c0ff0Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72if Te6edf3prev_bytes Tff7b72> T79c0ff100_000 Tff7b72and Te6edf3curr_bytes Tff7b72< Te6edf3prev_bytes Tff7b72* Te6edf3MIN_SIZE_RATIOTb4b4b4:
Tff7b72return Tff7b72True
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffcheck_db_health_at_openTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
T8b949e"""
Run integrity and baseline checks after opening the database.
Returns a list of human-readable issue strings; empty if healthy.
"""
Te6edf3issues Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72tryTb4b4b4:
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3integrity_rowsTb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffDatabase integrity check failed: no resultTa5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: no resultTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3first Tff7b72= Te6edf3integrity_rowsTb4b4b4[T79c0ff0Tb4b4b4]
Te6edf3val Tff7b72= Tffa657listTb4b4b4(Te6edf3firstTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4[T79c0ff0Tb4b4b4] Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3firstTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Te6edf3firstTb4b4b4[T79c0ff0Tb4b4b4]
Tff7b72if Te6edf3val Tff7b72!= Ta5d6ff"Ta5d6ffokTa5d6ff"Tb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase integrity check failed: Tffd700{Te6edf3valTffd700!s}Ta5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3valTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffDatabase integrity check error: Tffd700{Te6edf3eTffd700!s}Ta5d6ff"
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3msgTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3current Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_db_content_statsTb4b4b4(Tb4b4b4)
Te6edf3baseline Tff7b72= Tff7b72selfTff7b72.Td2a8ff_read_backup_baselineTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_is_backup_suspiciousTb4b4b4(Te6edf3currentTb4b4b4, Te6edf3baselineTb4b4b4)Tb4b4b4:
Te6edf3prev_c Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3prev_b Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_c Tff7b72= Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_b Tff7b72= Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3msg Tff7b72= Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDatabase content anomaly: was Tffd700{Te6edf3prev_cTffd700}Ta5d6ff messages / Tffd700{Te6edf3prev_bTffd700}Ta5d6ff bytes, Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffnow Tffd700{Te6edf3curr_cTffd700}Ta5d6ff / Tffd700{Te6edf3curr_bTffd700}Ta5d6ff. Restore from backup if needed.Ta5d6ff"
Tb4b4b4)
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3msgTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffinfoTb4b4b4(
Ta5d6ff"Ta5d6ffDB open health check ok: messages=Tffd700%sTa5d6ff total_bytes=Tffd700%sTa5d6ff"Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check (baseline): Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tff7b72return Te6edf3issues
Tff7b72def Td2a8ffcheck_db_health_at_closeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
T8b949e"""
Run health checks before closing the database (for logging only).
Returns a list of issue strings; empty if healthy.
"""
Te6edf3issues Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72tryTb4b4b4:
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3integrity_rowsTb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffDatabase integrity check failed: no resultTa5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: no resultTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3first Tff7b72= Te6edf3integrity_rowsTb4b4b4[T79c0ff0Tb4b4b4]
Te6edf3val Tff7b72= Tffa657listTb4b4b4(Te6edf3firstTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4[T79c0ff0Tb4b4b4] Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3firstTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Te6edf3firstTb4b4b4[T79c0ff0Tb4b4b4]
Tff7b72if Te6edf3val Tff7b72!= Ta5d6ff"Ta5d6ffokTa5d6ff"Tb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase integrity check failed: Tffd700{Te6edf3valTffd700!s}Ta5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: integrity failedTa5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3current Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_db_content_statsTb4b4b4(Tb4b4b4)
Te6edf3baseline Tff7b72= Tff7b72selfTff7b72.Td2a8ff_read_backup_baselineTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_is_backup_suspiciousTb4b4b4(Te6edf3currentTb4b4b4, Te6edf3baselineTb4b4b4)Tb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(
Ta5d6ff"Ta5d6ffDatabase content anomaly detected at close. Consider restoring from backup.Ta5d6ff"
Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: content anomalyTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffinfoTb4b4b4(
Ta5d6ff"Ta5d6ffDB close health check ok: messages=Tffd700%sTa5d6ff total_bytes=Tffd700%sTa5d6ff"Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check (baseline): Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tff7b72return Te6edf3issues
Tff7b72def Td2a8ff_database_pathsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3db_path Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffdb_path
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4: Te6edf3db_pathTb4b4b4,
Ta5d6ff"Ta5d6ffwalTa5d6ff"Tb4b4b4: Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-walTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffshmTa5d6ff"Tb4b4b4: Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-shmTa5d6ff"Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffget_database_health_snapshotTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3page_size Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffpage_sizeTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4) Tff7b72or T79c0ff0
Te6edf3page_count Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffpage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4) Tff7b72or T79c0ff0
Te6edf3freelist_pages Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6fffreelist_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4) Tff7b72or T79c0ff0
Te6edf3free_bytes Tff7b72= Tb4b4b4(
Te6edf3page_size Tff7b72* Te6edf3freelist_pages Tff7b72if Te6edf3page_size Tff7b72> T79c0ff0 Tff7b72and Te6edf3freelist_pages Tff7b72> T79c0ff0 Tff7b72else T79c0ff0
Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffjournal_modeTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffjournal_modeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffsynchronousTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffsynchronousTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffwal_autocheckpointTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffwal_autocheckpointTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffauto_vacuumTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffauto_vacuumTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffpage_sizeTa5d6ff"Tb4b4b4: Te6edf3page_sizeTb4b4b4,
Ta5d6ff"Ta5d6ffpage_countTa5d6ff"Tb4b4b4: Te6edf3page_countTb4b4b4,
Ta5d6ff"Ta5d6fffreelist_pagesTa5d6ff"Tb4b4b4: Te6edf3freelist_pagesTb4b4b4,
Ta5d6ff"Ta5d6ffestimated_free_bytesTa5d6ff"Tb4b4b4: Te6edf3free_bytesTb4b4b4,
Ta5d6ff"Ta5d6fffilesTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_database_file_statsTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_checkpoint_walTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3modeTb4b4b4: Tffa657str Tff7b72= Ta5d6ff"Ta5d6ffTRUNCATETa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffPRAGMA wal_checkpoint(Tffd700{Te6edf3modeTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8fffetchallTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffrun_database_vacuumTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
T8b949e# Attempt to checkpoint WAL, ignore errors if busy
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffWarning: WAL checkpoint during vacuum failed (non-critical): Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffVACUUMTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffhealthTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffget_database_health_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
T8b949e# Wrap in a cleaner error message
Tff7b72raise Tf85149ExceptionTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase vacuum failed: Tffd700{Te6edf3eTffd700!s}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffrun_database_recoveryTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3actions Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffquick_check_beforeTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffwal_checkpointTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)Tb4b4b4}Tb4b4b4)
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Te6edf3integrity Tff7b72= Tb4b4b4[Te6edf3rowTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3integrity_rowsTb4b4b4] Tff7b72if Te6edf3integrity_rows Tff7b72else Tb4b4b4[Tb4b4b4]
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffintegrity_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Te6edf3integrityTb4b4b4}Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffvacuumTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffquick_check_afterTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffactionsTa5d6ff"Tb4b4b4: Te6edf3actionsTb4b4b4,
Ta5d6ff"Ta5d6ffhealthTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffget_database_health_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_checkpoint_and_closeTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to checkpoint WAL: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to close database: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffcloseTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTb4b4b4, Ta5d6ff"Ta5d6ffproviderTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffclose_allTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTb4b4b4, Ta5d6ff"Ta5d6ffproviderTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffclose_allTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ff_backup_to_zipTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3backup_pathTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3paths Tff7b72= Tff7b72selfTff7b72.Td2a8ff_database_pathsTb4b4b4(Tb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
T8b949e# ensure WAL is checkpointed to get a consistent snapshot
Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)
Te6edf3main_filename Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72with Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4(Te6edf3backup_pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4, Te6edf3compressionTff7b72=Te6edf3zipfileTff7b72.Td2a8ffZIP_DEFLATEDTb4b4b4) Tff7b72as Te6edf3zfTb4b4b4:
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3arcnameTff7b72=Te6edf3main_filenameTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffwalTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4:
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffwalTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3arcnameTff7b72=Ta5d6fffTa5d6ff"Tffd700{Te6edf3main_filenameTffd700}Ta5d6ff-walTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffshmTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4:
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffshmTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3arcnameTff7b72=Ta5d6fffTa5d6ff"Tffd700{Te6edf3main_filenameTffd700}Ta5d6ff-shmTa5d6ff"Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffpathTa5d6ff"Tb4b4b4: Te6edf3backup_pathTb4b4b4,
Ta5d6ff"Ta5d6ffsizeTa5d6ff"Tb4b4b4: Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffbackup_databaseTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3storage_pathTb4b4b4,
Te6edf3backup_pathTb4b4b4: Tffa657str Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Te6edf3max_countTb4b4b4: Tffa657int Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3default_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff"Tb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3default_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3timestamp Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffstrftimeTb4b4b4(Ta5d6ff"Ta5d6ff%Ta5d6ffYTa5d6ff%Ta5d6ffmTffd700%dTa5d6ff-Ta5d6ff%Ta5d6ffHTa5d6ff%Ta5d6ffMTa5d6ff%Ta5d6ffSTa5d6ff"Tb4b4b4)
Te6edf3current_stats Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_db_content_statsTb4b4b4(Tb4b4b4)
Te6edf3baseline Tff7b72= Tff7b72selfTff7b72.Td2a8ff_read_backup_baselineTb4b4b4(Te6edf3storage_pathTb4b4b4)
Te6edf3suspicious Tff7b72= Tff7b72selfTff7b72.Td2a8ff_is_backup_suspiciousTb4b4b4(Te6edf3current_statsTb4b4b4, Te6edf3baselineTb4b4b4)
Tff7b72if Te6edf3backup_path Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3suspiciousTb4b4b4:
Te6edf3backup_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(
Te6edf3default_dirTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffbackup-SUSPICIOUS-Tffd700{Te6edf3timestampTffd700}Ta5d6ff.zipTa5d6ff"
Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3backup_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3default_dirTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffbackup-Tffd700{Te6edf3timestampTffd700}Ta5d6ff.zipTa5d6ff"Tb4b4b4)
Te6edf3result Tff7b72= Tff7b72selfTff7b72.Td2a8ff_backup_to_zipTb4b4b4(Te6edf3backup_pathTb4b4b4)
Tff7b72if Te6edf3suspiciousTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(
Ta5d6ff"Ta5d6ffBackup data-loss guard: current DB looks wrong (was Tffd700%sTa5d6ff messages / Tffd700%sTa5d6ff bytes, now Tffd700%sTa5d6ff / Tffd700%sTa5d6ff); Ta5d6ff"
Ta5d6ff"Ta5d6ffwrote backup-SUSPICIOUS-*.zip, skipping rotationTa5d6ff"Tb4b4b4,
Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tffa657printTb4b4b4(
Ta5d6ff"Ta5d6ffBackup data-loss guard: current database looks wrong Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ff(was Tffd700{Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffmessage_countTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff messages / Tffd700{Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftotal_bytesTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff bytes, Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffnow Tffd700{Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffmessage_countTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff / Tffd700{Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftotal_bytesTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff). Ta5d6ff"
Ta5d6ff"Ta5d6ffWrote backup-SUSPICIOUS-*.zip; skipping rotation and baseline update. Check disk and DB.Ta5d6ff"
Tb4b4b4)
Te6edf3resultTb4b4b4[Ta5d6ff"Ta5d6ffsuspiciousTa5d6ff"Tb4b4b4] Tff7b72= Tff7b72True
Te6edf3resultTb4b4b4[Ta5d6ff"Ta5d6ffbaselineTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3baseline
Te6edf3resultTb4b4b4[Ta5d6ff"Ta5d6ffcurrent_statsTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3current_stats
Tff7b72return Te6edf3result
Tff7b72if Te6edf3max_count Tff7b72is Tff7b72not Tff7b72None Tff7b72and Te6edf3max_count Tff7b72> T79c0ff0Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3backups Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3file Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Te6edf3default_dirTb4b4b4)Tb4b4b4:
Tff7b72if Tb4b4b4(
Te6edf3fileTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff.zipTa5d6ff"Tb4b4b4)
Tff7b72and Te6edf3fileTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffbackup-Ta5d6ff"Tb4b4b4)
Tff7b72and Ta5d6ff"Ta5d6ffSUSPICIOUSTa5d6ff" Tff7b72not Tff7b72in Te6edf3file
Tb4b4b4)Tb4b4b4:
Te6edf3full_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3default_dirTb4b4b4, Te6edf3fileTb4b4b4)
Te6edf3stats Tff7b72= Te6edf3osTff7b72.Td2a8ffstatTb4b4b4(Te6edf3full_pathTb4b4b4)
Te6edf3backupsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Te6edf3full_pathTb4b4b4, Te6edf3statsTff7b72.Td2a8ffst_mtimeTb4b4b4)Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Te6edf3backupsTb4b4b4) Tff7b72> Te6edf3max_countTb4b4b4:
Te6edf3backupsTff7b72.Td2a8ffsortTb4b4b4(Te6edf3keyTff7b72=Tff7b72lambda Te6edf3xTb4b4b4: Te6edf3xTb4b4b4[T79c0ff1Tb4b4b4]Tb4b4b4)
Te6edf3to_delete Tff7b72= Te6edf3backupsTb4b4b4[Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3backupsTb4b4b4) Tff7b72- Te6edf3max_countTb4b4b4]
Tff7b72for Te6edf3pathTb4b4b4, Te6edf3_ Tff7b72in Te6edf3to_deleteTb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3pathTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to cleanup old backups: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_write_backup_baselineTb4b4b4(
Te6edf3storage_pathTb4b4b4,
Te6edf3current_statsTb4b4b4[Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3current_statsTb4b4b4[Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3result
Tff7b72def Td2a8ffcreate_snapshotTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4, Te6edf3nameTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
T8b949e"""Creates a named snapshot of the database."""
Te6edf3snapshot_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"Tb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3snapshot_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
T8b949e# Ensure name is safe for filesystem
Te6edf3safe_name Tff7b72= Ta5d6ff"Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(
Tb4b4b4[Te6edf3c Tff7b72for Te6edf3c Tff7b72in Te6edf3name Tff7b72if Te6edf3cTff7b72.Td2a8ffisalnumTb4b4b4(Tb4b4b4) Tff7b72or Te6edf3c Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ff Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff_Ta5d6ff"Tb4b4b4)Tb4b4b4]Tb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3safe_nameTb4b4b4:
Te6edf3safe_name Tff7b72= Ta5d6ff"Ta5d6ffunnamed_snapshotTa5d6ff"
Te6edf3snapshot_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3snapshot_dirTb4b4b4, Ta5d6fffTa5d6ff"Tffd700{Te6edf3safe_nameTffd700}Ta5d6ff.zipTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ff_backup_to_zipTb4b4b4(Te6edf3snapshot_pathTb4b4b4)
Tff7b72def Td2a8fflist_snapshotsTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
T8b949e"""Lists all available snapshots."""
Te6edf3snapshot_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3snapshot_dirTb4b4b4)Tb4b4b4:
Tff7b72return Tb4b4b4[Tb4b4b4]
Te6edf3snapshots Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3file Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Te6edf3snapshot_dirTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3fileTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff.zipTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3full_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3snapshot_dirTb4b4b4, Te6edf3fileTb4b4b4)
Te6edf3stats Tff7b72= Te6edf3osTff7b72.Td2a8ffstatTb4b4b4(Te6edf3full_pathTb4b4b4)
Te6edf3snapshotsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3fileTb4b4b4[Tb4b4b4:Tff7b72-T79c0ff4Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffpathTa5d6ff"Tb4b4b4: Te6edf3full_pathTb4b4b4,
Ta5d6ff"Ta5d6ffsizeTa5d6ff"Tb4b4b4: Te6edf3statsTff7b72.Td2a8ffst_sizeTb4b4b4,
Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8fffromtimestampTb4b4b4(
Te6edf3statsTff7b72.Td2a8ffst_mtimeTb4b4b4,
Te6edf3UTCTb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72return Tffa657sortedTb4b4b4(Te6edf3snapshotsTb4b4b4, Te6edf3keyTff7b72=Tff7b72lambda Te6edf3xTb4b4b4: Te6edf3xTb4b4b4[Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3reverseTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72def Td2a8ffdelete_snapshot_or_backupTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3storage_pathTb4b4b4,
Te6edf3filenameTb4b4b4: Tffa657strTb4b4b4,
Te6edf3is_backupTb4b4b4: Tffa657bool Tff7b72= Tff7b72FalseTb4b4b4,
Tb4b4b4)Tb4b4b4:
T8b949e"""Deletes a database snapshot or auto-backup."""
Te6edf3base_dir Tff7b72= Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff" Tff7b72if Te6edf3is_backup Tff7b72else Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"
Te6edf3file_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Te6edf3base_dirTb4b4b4, Te6edf3filenameTb4b4b4)
T8b949e# Basic security check to ensure we stay within the intended directory
Te6edf3abs_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3file_pathTb4b4b4)
Te6edf3abs_base Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Te6edf3base_dirTb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3abs_pathTff7b72.Td2a8ffstartswithTb4b4b4(Te6edf3abs_baseTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffInvalid pathTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3abs_pathTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3abs_pathTb4b4b4)
Tff7b72return Tff7b72True
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffrestore_databaseTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3backup_pathTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffBackup not found at Tffd700{Te6edf3backup_pathTffd700}Ta5d6ff"
Tff7b72raise Tf85149FileNotFoundErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3paths Tff7b72= Tff7b72selfTff7b72.Td2a8ff_database_pathsTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_checkpoint_and_closeTb4b4b4(Tb4b4b4)
T8b949e# clean existing files
Tff7b72for Te6edf3p Tff7b72in Te6edf3pathsTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3pTb4b4b4)
Tff7b72if Te6edf3zipfileTff7b72.Td2a8ffis_zipfileTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4:
Tff7b72with Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4(Te6edf3backup_pathTb4b4b4, Ta5d6ff"Ta5d6ffrTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3zfTb4b4b4:
Te6edf3zfTff7b72.Td2a8ffextractallTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3shutilTff7b72.Td2a8ffcopy2Tb4b4b4(Te6edf3backup_pathTb4b4b4, Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)
T8b949e# reopen and retune
Tff7b72selfTff7b72.Td2a8ffinitializeTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Te6edf3integrity Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffrestored_fromTa5d6ff"Tb4b4b4: Te6edf3backup_pathTb4b4b4,
Ta5d6ff"Ta5d6ffintegrity_checkTa5d6ff"Tb4b4b4: Te6edf3integrityTb4b4b4,
Tb4b4b4}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────